/**
 * 
 */
package org.vsg.stock.basic.service.business;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vsg.stock.basic.domain.BalanceLine;
import org.vsg.stock.basic.domain.BalanceLineFactory;
import org.vsg.stock.basic.domain.BalanceLineKeyPoint;
import org.vsg.stock.basic.service.StockBusinessHelper;
import org.vsg.stock.core.domain.StockDailyKLine;
import org.vsg.stock.core.indicator.KLineStockIndicatorModel;
import org.vsg.stock.core.indicator.ZigIndicator;
import org.vsg.stock.core.indicator.kline.ZigIndicatorResultItem;

/**
 * use zig analysis to find the key 
 * @author Bill Vison
 *
 */
public class ZigBottomBalanceLineFinderBO extends
	OrgDataStockBalanceLineFinderBO {

	private static final Logger logger = LoggerFactory.getLogger(ZigBottomBalanceLineFinderBO.class);

	private BalanceLineFactory factory = BalanceLineFactory.getInstance();
	
	private static final String FINDER_CODE = "zig_bottom_balance_line";
	
	@Override
	public String getFinderCode() {
		// TODO Auto-generated method stub
		return FINDER_CODE;
	}	
	
	@Override
	protected List<BalanceLine> scanLines(List<StockDailyKLine>  stkKlineList) {

		// --- scan and mark point in to list and map ---
		KLineStockIndicatorModel klineStkIndiModel = 
			new KLineStockIndicatorModel(stkKlineList ,KLineStockIndicatorModel.KLinePosition.LOW);

		ZigIndicator zigInd = new ZigIndicator();
		// 过细,将无法分出波,过大找的波较少,对于价来说, 3-3.3为合适
		zigInd.setThreshold( (double)3.2 );
		zigInd.setIndicatorModel( klineStkIndiModel );
		
		Map<String, java.io.Serializable > resultMap  = zigInd.calculate();		

		// --- use one point value check ---
		List<ZigIndicatorResultItem> troughs = (List<ZigIndicatorResultItem>) resultMap.get( ZigIndicator.ALL_TROUGH );
		
		Map<Double, Set<GoupingPointRefStockDailyKLine>> groupingFinalMap = groupFinalPriceMap(troughs);
		
		// --- filter and remove object ---
		groupingFinalMap = filterAndRemoveFinalPriceMap(groupingFinalMap);

		List<BalanceLine> allNewLines = new Vector<BalanceLine>();			
		// --- build group line & point ---
		Entry<Double, Set<GoupingPointRefStockDailyKLine>> entry = null;
		
		for (Iterator<Entry<Double, Set<GoupingPointRefStockDailyKLine>>> iter = groupingFinalMap.entrySet().iterator() ; iter.hasNext(); ) {
			
			entry = iter.next();
			
			Set<GoupingPointRefStockDailyKLine> values = entry.getValue();
			
			BalanceLine aNewLine = factory.createLine(stkKlineList.get(0).getId().getStockCode(), BalanceLine.FORM_H);
			aNewLine.setForm(BalanceLine.FORM_H);
			for (Iterator<GoupingPointRefStockDailyKLine> klineIter = values.iterator() ; klineIter.hasNext(); ) {
				GoupingPointRefStockDailyKLine groupingDailyKline = klineIter.next();
				
				BalanceLineKeyPoint blkPoint = factory.createPoint(groupingDailyKline.getReferPrice(), groupingDailyKline);
				
				aNewLine.addKeyPoint( blkPoint );				
			}
			allNewLines.add(aNewLine);			

		}



		return allNewLines;
	}
	
	private Map<Double, Set<GoupingPointRefStockDailyKLine>> filterAndRemoveFinalPriceMap(Map<Double, Set<GoupingPointRefStockDailyKLine>> groupingFinalMap) {
		
		Entry<Double, Set<GoupingPointRefStockDailyKLine>> entry = null;
		for (Iterator<Entry<Double, Set<GoupingPointRefStockDailyKLine>>> iter = groupingFinalMap.entrySet().iterator() ; iter.hasNext(); ) {
			
			entry = iter.next();
			
			// --- remove one instance ---
			if (entry.getValue().size() == 1 ) {
				GoupingPointRefStockDailyKLine  dkline = entry.getValue().iterator().next();
				double bottomPrice = dkline.getClose() > dkline.getOpen() ? dkline.getOpen() : dkline.getClose();
				if (dkline.getReferPrice() > bottomPrice ) {
					// --- remove entry ---
					iter.remove();
				}
			}
		}
		
		
		return groupingFinalMap;
		
	}
	
	
	private Map<Double, Set<GoupingPointRefStockDailyKLine>> groupFinalPriceMap(List<ZigIndicatorResultItem> troughs) {
		
		Map<Double, Set<GoupingPointRefStockDailyKLine>> finalGroupingKLineMap = new HashMap<Double,Set<GoupingPointRefStockDailyKLine>> ();
		
		Map<Double, List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice = firstGroupPointForLine(troughs);
		
		Map<GoupingPointRefStockDailyKLine , List<Double>> refStkDailymap = convertToKlinePriceMap(groupingKLineByPrice);
		
		
		// ---- merge to online line to defined ----
		Set<GoupingPointRefStockDailyKLine> refStkKLineSet = refStkDailymap.keySet();
		List<GoupingPointRefStockDailyKLine> groupingSortList = null;
		
		for (GoupingPointRefStockDailyKLine kline : refStkKLineSet) {
			
			List<Double> priceList = refStkDailymap.get(kline) ;
			
			Set<GoupingPointRefStockDailyKLine> groupingSortHashSet = new LinkedHashSet<GoupingPointRefStockDailyKLine>();
			
			for (int i = 0 ; i < priceList.size() ; i++) {
				groupingSortHashSet.addAll( groupingKLineByPrice.get( priceList.get(i) ) );
			}
			
			groupingSortList = new LinkedList<GoupingPointRefStockDailyKLine>(groupingSortHashSet);
			if ( groupingSortList.size() > 2) {
				Collections.sort(groupingSortList ,new GoupingPointStockDailyKLineComparator());
			}

			
			// --- find the middle point and group in to a new map  ---
			int modIndex = groupingSortList.size() % 2;
			int findIndex = groupingSortList.size() / 2;
			double standPrice = 0;
			if (modIndex != 0) {
				GoupingPointRefStockDailyKLine gkline = groupingSortList.get(findIndex);
				standPrice = gkline.getReferPrice();
			}
			else {
				standPrice = ( groupingSortList.get(findIndex-1).getReferPrice() +  groupingSortList.get(findIndex).getReferPrice() ) / 2;
			}
			
			
			// ---- grouping for the new price ---
			double max = StockBusinessHelper.getDiffRelativedPrice(standPrice, StockBusinessHelper.MAX_DIFF);
			double min = StockBusinessHelper.getDiffRelativedPrice(standPrice, StockBusinessHelper.MIN_DIFF);			
			
			
			// --- map a new set ---
			Set<GoupingPointRefStockDailyKLine> finalGroupingSet = new LinkedHashSet<GoupingPointRefStockDailyKLine>();
			for (GoupingPointRefStockDailyKLine tmp : groupingSortList) {
				if (tmp.getReferPrice() <= max && tmp.getReferPrice() >= min) {
					finalGroupingSet.add( tmp );
				}
			}
			finalGroupingKLineMap.put( standPrice , finalGroupingSet);
			
		}		
		
		
		return finalGroupingKLineMap;
		
	}
	

	private Map<GoupingPointRefStockDailyKLine , List<Double>> convertToKlinePriceMap(Map<Double, List<GoupingPointRefStockDailyKLine>>  groupingKLineByPrice) {
		
		Map<GoupingPointRefStockDailyKLine , List<Double>> klinePriceMap = new HashMap<GoupingPointRefStockDailyKLine , List<Double>>();
		
		for (Iterator<Double>  pricekeyIter =  groupingKLineByPrice.keySet().iterator() ; pricekeyIter.hasNext() ; ) {
			Double price = pricekeyIter.next();
			List<GoupingPointRefStockDailyKLine> klineList = groupingKLineByPrice.get(price);
			
			// --- not empry ---
			boolean created = false;
			List<Double> assoList = null;
			// --- read the line ---
			for (GoupingPointRefStockDailyKLine dailyKLine : klineList) {

/*
				if ( ( df.format( dailyKLine.getDate() ).equals("2009-11-13") 
						&& (dailyKLine.getRefType() == StockDailyKLine.OPEN) ) 
						|| 
						(df.format( dailyKLine.getDate() ).equals("2009-11-02") && (dailyKLine.getRefType() == StockDailyKLine.CLOSE)) ) {
					System.out.println(" handle ----------- " + dailyKLine.getReferPrice() + " stan " + price);
				}							
*/				
				// --- build the same group ---
				if (assoList == null) {
					assoList = klinePriceMap.get( dailyKLine );
					if ( !created ) {
						assoList = new Vector<Double>();
						created = true;
					}
				}

				if ( !assoList.contains( price ) ) {
					assoList.add( price );
				}
				klinePriceMap.put( dailyKLine, assoList);
			}
			
		}
		
		return klinePriceMap;
	}
	
	private Map<Double, List<GoupingPointRefStockDailyKLine>> firstGroupPointForLine(List<ZigIndicatorResultItem> shownTroughs) {
		
		Map<Double,List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice = new HashMap<Double,List<GoupingPointRefStockDailyKLine>> ();
		StockDailyKLine kline = null;
		
		for (ZigIndicatorResultItem<StockDailyKLine> item : shownTroughs) {
			
			kline = item.getAssoRef();
			
			Map<Double,String> mergePriceRecord = StockBusinessHelper.mergeToPriceMap(kline);
			Set<Double> mergePriceKeies = mergePriceRecord.keySet();		
			for (double mergePrice : mergePriceKeies) {

				double[] outputs = searchGroupingLinePrice(groupingKLineByPrice , mergePrice);
				
				for (double output : outputs) {
					
					List<GoupingPointRefStockDailyKLine> listContainer = groupingKLineByPrice.get(output);
					if (listContainer == null) {
						 listContainer = new Vector<GoupingPointRefStockDailyKLine>();
					}
					if (!listContainer.contains( kline )) {
						GoupingPointRefStockDailyKLine gprKline = new GoupingPointRefStockDailyKLine(kline , output);
						if ( !listContainer.contains( gprKline ) ) {
							listContainer.add( gprKline );
						}
					}
					groupingKLineByPrice.put(  output  , listContainer);
					
				}
			}
		}
	
		return groupingKLineByPrice;
	}

	private double[] searchGroupingLinePrice(Map<Double,List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice , double input) {

		double max = StockBusinessHelper.getDiffRelativedPrice(input, StockBusinessHelper.MAX_DIFF);
		double min = StockBusinessHelper.getDiffRelativedPrice(input, StockBusinessHelper.MIN_DIFF);

		Set<Double> keyPrice = groupingKLineByPrice.keySet();
		
		// --- find the pattern price to the list ---
		Set<Double> patternPrice = new HashSet<Double>();
		for (Double p : keyPrice) {
			
			if (p <= max && p >= min) {
				patternPrice.add(p);
			}
		}
		
		// return the input price when not fould in price map
		if ( patternPrice.size() == 0) {
			double[] output = new double[1];
			output[0] = input;
			return output;
		}
		
		
		// --- find the close price if have some pattern price
		double lastTmpDiff = 999999999;
		double[] output = new double[patternPrice.size()];
		int index = 0;

		for (Iterator<Double> pattIter = patternPrice.iterator() ; pattIter.hasNext(); ) {
			
			double currentPrice = pattIter.next();
			
			output[index] = currentPrice;
		
			pattIter.remove();
			
			index = index + 1;
		}
		
		
		return output;
	}

	
	
	
}
